package com.itheima.appserver.service;

import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.Method;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.alibaba.dubbo.config.annotation.Reference;
import com.itheima.appserver.config.HuanXinConfig;
import com.itheima.commons.constants.Constants;
import com.itheima.commons.model.domain.HuanxinUser;
import com.itheima.dubbo.interfaces.HuanxinUserApi;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * 环信业务类
 */
@Service
public class HuanXinService {

    @Reference
    private HuanxinUserApi huanxinUserApi;

    @Autowired
    private HuanXinConfig huanXinConfig;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    /**
     * 根据用户ID查询环信的账号和密码
     */
    public Map<String, String> queryHuanXinUserByUserId(Long userId) {
        HuanxinUser huanxinUser = huanxinUserApi.queryHuanXinUserByUserId(userId);
        HashMap<String, String> hashMap = new HashMap<>();
        hashMap.put("username", huanxinUser.getUsername());
        hashMap.put("password", huanxinUser.getPassword());
        return hashMap;
    }


    /**
     * 根据环信的用户名查询环信的用户名和密码
     *
     * @return
     */
    public HuanxinUser queryHuanXinUserByHxUserName(String hxUsername) {
        return huanxinUserApi.queryHuanXinUserByUserUserName(hxUsername);
    }

    /**
     * 注册用户到环信平台
     *
     * @return
     */
    public Boolean registerUserToHuanxin(Long userId) {
        HuanxinUser hxuser = HuanxinUser.builder()
                .userId(userId)
                .username("HX_" + userId)
                .password(SecureUtil.md5(UUID.fastUUID().toString(true)))
                .created(new Date())
                .updated(new Date())
                .build();

        // 保存到MySQL中
        huanxinUserApi.saveHuanxinUser(hxuser);

        // 给环信发送注册好友的请求
        String targetUrl = this.huanXinConfig.getUrl()
                + this.huanXinConfig.getOrgName() + "/" +
                this.huanXinConfig.getAppName() + "/users";

        HttpResponse response = this.execute(targetUrl, JSONUtil.createObj()
                        .set("username", hxuser.getUsername())
                        .set("password", hxuser.getPassword())
                        .toString()
                , Method.POST);

        return response.isOk();
    }


    /**
     * 环信添加好友
     *
     * @param userId
     * @param friendId
     * @return
     */
    public Boolean addUserFriend(Long userId, Long friendId) {
        String targetUrl = this.huanXinConfig.getUrl()
                + this.huanXinConfig.getOrgName() + "/"
                + this.huanXinConfig.getAppName() + "/users/HX_" +
                userId + "/contacts/users/HX_" + friendId;

        return this.execute(targetUrl, null, Method.POST).isOk();
    }

    /**
     * 环信删除好友
     *
     * @param userId
     * @param friendId
     * @return
     */
    public Boolean removeUserFriend(Long userId, Long friendId) {
        String targetUrl = this.huanXinConfig.getUrl()
                + this.huanXinConfig.getOrgName() + "/"
                + this.huanXinConfig.getAppName() + "/users/HX_" +
                userId + "/contacts/users/HX_" + friendId;

        return this.execute(targetUrl, null, Method.DELETE).isOk();
    }


    /**
     * 环信发送文本消息
     *
     * @param targetUserName
     * @param msg
     * @return
     */
    public Boolean sendMsgFromAdmin(String targetUserName, String msg) {
        String targetUrl = this.huanXinConfig.getUrl()
                + this.huanXinConfig.getOrgName() + "/"
                + this.huanXinConfig.getAppName() + "/messages";

        String body = JSONUtil.createObj()
                .set("target_type", "users")
                .set("target", JSONUtil.createArray().set(targetUserName))
                .set("msg", JSONUtil.createObj()
                        .set("type", "txt")
                        .set("msg", msg)).toString();
        return this.execute(targetUrl, body, Method.POST).isOk();
    }


    /**
     * 获取环信的请求Token
     *
     * @return
     */
    public String getToken() {
        String token = this.redisTemplate.opsForValue().get(Constants.HX_USER_PREFIX + "_token");
        if (StrUtil.isNotEmpty(token)) {
            return token;
        }

        //请求获取token
        return this.refreshToken();
    }

    /**
     * 刷新token，请求环信接口，将token存储到redis中
     *
     * @return
     */
    private String refreshToken() {
        String targetUrl = huanXinConfig.getUrl() +
                huanXinConfig.getOrgName() + "/" + huanXinConfig.getAppName() + "/token";

        // 请求体参数
        Map<String, Object> param = new HashMap<>();
        param.put("grant_type", "client_credentials");
        param.put("client_id", huanXinConfig.getClientId());
        param.put("client_secret", huanXinConfig.getClientSecret());

        HttpResponse response = HttpRequest.post(targetUrl)
                .body(JSONUtil.toJsonStr(param))
                .timeout(20000) //请求超时时间
                .execute();

        if (response.isOk()) {
            // 获取响应体   {"",""}
            String jsonBody = response.body();
            // 解析响应体
            JSONObject jsonObject = JSONUtil.parseObj(jsonBody);
            // 获取Token
            String token = jsonObject.getStr("access_token");
            if (StrUtil.isNotEmpty(token)) {
                //将token数据缓存到redis中，缓存时间由expires_in决定
                //提前1小时失效
                long timeout = jsonObject.getLong("expires_in") - 3600;
                this.redisTemplate.opsForValue().set(Constants.HX_USER_PREFIX + "_token", token, timeout, TimeUnit.SECONDS);
                return token;
            }
        }
        return null;
    }

    /**
     * 通用的发送请求方法
     *
     * @param url    请求地址
     * @param body   请求参数
     * @param method 请求方法
     * @return
     */
    @Retryable(value = Exception.class, maxAttempts = 5, backoff = @Backoff(delay = 2000L, multiplier = 2))
    private HttpResponse execute(String url, String body, Method method) {
        // 获取Token 先从Redis中获取  如果没有再给环信发送请求获取
        String token = this.getToken();

        HttpRequest httpRequest;

        switch (method) {
            case POST: {
                httpRequest = HttpRequest.post(url);
                break;
            }
            case DELETE: {
                httpRequest = HttpRequest.delete(url);
                break;
            }
            case PUT: {
                httpRequest = HttpRequest.put(url);
                break;
            }
            case GET: {
                httpRequest = HttpRequest.get(url);
                break;
            }
            default: {
                return null;
            }
        }

        HttpResponse response = httpRequest
                .header("Content-Type", "application/json") //设置请求内容类型
                .header("Authorization", "Bearer " + token)  //设置token
                .body(body) // 设置请求数据
                .timeout(20000) // 超时时间
                .execute(); // 执行请求

        // 401 说明没有权限 也就是Token失效了
        // 如果判断是 != 200 那么我们还要处理Token是否为空 不能频繁的去请求Token的生成 有可能被封号
        if (response.getStatus() == 401) {
            //token失效，重新刷新token
            this.refreshToken();
            //抛出异常，需要进行重试
            throw new RuntimeException();
        }

        return response;
    }

    /**
     * 全部重试失败后执行
     *
     * @param e
     * @return
     */
    @Recover
    private HttpResponse recover(Exception e) {
        //如果重试5次后，依然不能获取到token，说明网络或账号出现了问题，只能返回null了，后续的请求将无法再执行
        return null;
    }

}
